/*******************************************************************************
 * Copyright (c) 2012, 2013 XML-Print Project.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     XML-Print Project - initial API and implementation
 ******************************************************************************/
package info.xmlprint.ui.referencesystem;

import info.xmlprint.core.referencesystem.AbstractBaseGroup;
import info.xmlprint.core.referencesystem.AbstractBaseItem;
import info.xmlprint.core.referencesystem.ReferenceFieldGroup;
import info.xmlprint.core.referencesystem.ReferencingSystem;
import info.xmlprint.core.referencesystem.ReferencingSystemGroup;
import info.xmlprint.run.RuntimeUtil;
import info.xmlprint.ui.message.GuiMessages;
import info.xmlprint.ui.widgets.IObserverControl;
import info.xmlprint.ui.widgets.ISubjectControl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class ReferencingSystemComposite extends Composite implements
		ISubjectControl {

	private List<IObserverControl> observerList = new ArrayList<IObserverControl>();

	private ReferencingSystem referencingSystem;

	private ReferencingSystemGroup referencingSystemGroup;

	private Text nameText, schemaText;

	private org.eclipse.swt.widgets.List counterItemList, counterItemList_typographic, 
		counterItemList_specialChars;

	private ReferencingSystemComposite _this = this;

	private Cursor cursor;

	private ReferenceFieldGroup referenceFieldGroup;

	private boolean canInsert = true;

	// TODO: String from GuiMessages
	private String[] counterPageItem = new String[] { GuiMessages.Fieldinformation_PageNumber,
			"${pagenumber}" };
	private String[] counterColumnItem = new String[] { GuiMessages.Fieldinformation_ColumnNumber,
			"${colnumber}" };
	private String[] counterRowItem = new String[] { GuiMessages.Fieldinformation_LineNumber,
			"${linenumber}" };

	private String[][] relationItems = new String[][] {
			{ "equal", "identisch ist mit dem" },
			{ "greater", "größer ist als der" },
			{ "smaller", "kleiner ist als der" },
			{ "atLeast", "mindestens so groß ist wie der" },
			{ "atMost", "höchstens so groß ist wie der" } };
	private String[][] referenceItems = new String[][] {
			{ "last", "vorherige(n) Wert" }, { "", "bestimmte(n) Wert" } };
	private String[][] actionItems = new String[][] {
			{ "noText", "gib Wert nicht aus" }, { "otherText", "gib folgenden Text aus" } };

	private Map<Integer, String[]> counterItemMap = new HashMap<Integer, String[]>();
	private Map<Integer, String[]> counterItemMap_typographic = new HashMap<Integer, String[]>();
	private Map<Integer, String[]> counterItemMap_specialChars = new HashMap<Integer, String[]>();
	
	private Combo initial, relation, reference, action;
	private Text reference_specific, actionText;
	private Button exception;
	private int id = 0;
	private String currentId;

	public ReferencingSystemComposite(Composite parent, int style) {
		super(parent, style);
		id = 0;
		createControls(); 
	}

	private void createControls() {
		this.setLayout(new GridLayout());
		Label nameLabel = new Label(this, SWT.NONE);
		nameLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		nameLabel.setText("Name"); // TODO

		nameText = new Text(this, SWT.BORDER);

		nameText.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				canInsert = false;
			}

			@Override
			public void focusLost(FocusEvent e) {
				// TODO Auto-generated method stub

			}
		});

		Label schemaLabel = new Label(this, SWT.NONE);
		schemaLabel.setText("Schemaaufbau"); // //$NON-NLS-N$

		schemaText = new Text(this, SWT.BORDER);
		schemaText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		schemaText.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				canInsert = true;
				cursor = schemaText.getCursor();
			}

			@Override
			public void focusLost(FocusEvent e) {
			}
		});

		
		Group countGroup = new Group(this, SWT.NONE);
		countGroup.setLayoutData(new GridData(GridData.FILL_BOTH));
		countGroup.setText(GuiMessages.Fieldinformation_Predefined_Counter);
		countGroup.setLayout(new FillLayout());

		/*
		 * Tab for reference fields 
		 */
		
		final TabFolder tabFolder = new TabFolder(countGroup, SWT.BORDER);
		
		TabItem tabItem = new TabItem(tabFolder, SWT.NULL);
	    tabItem.setText("Referenzfelder");//$NON-NLS-N$
	    counterItemList = new org.eclipse.swt.widgets.List(tabFolder, SWT.NONE);
	    tabItem.setControl(getTabControl(tabFolder, counterItemList, counterItemMap));
	    
	    /*
	     * Tab for typographic elements
	     */
	    tabItem = new TabItem(tabFolder, SWT.NULL);
	    tabItem.setText("Typographische Zähler"); //$NON-NLS-N$
	    counterItemList_typographic = new org.eclipse.swt.widgets.List(tabFolder, SWT.NONE);
	    tabItem.setControl(getTabControl(tabFolder, counterItemList_typographic, counterItemMap_typographic));
	    
	    // special chars
		tabItem = new TabItem(tabFolder, SWT.NULL);
	    tabItem.setText("Sonderzeichen"); //$NON-NLS-N$
	    counterItemList_specialChars = new org.eclipse.swt.widgets.List(tabFolder, SWT.NONE);
	    tabItem.setControl(getTabControl(tabFolder, counterItemList_specialChars, counterItemMap_specialChars));
	    

		Composite exceptionComp = new Composite(this, SWT.NONE);
		exceptionComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		exceptionComp.setLayout(new GridLayout(6, false));

		exception = new Button(exceptionComp, SWT.CHECK);
		exception.setText("");
		exception.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				boolean status = exception.getSelection();
				initial.setEnabled(status);
				relation.setEnabled(status);
				
				reference.setEnabled(status);
				reference.notifyListeners(SWT.Selection ,new Event());
				
				action.setEnabled(status);
				action.notifyListeners(SWT.Selection, new Event());
			}
		});

		Label label = new Label(exceptionComp, SWT.NONE);
		label.setText("Falls der aktuelle Wert des Zählers "); //TODO //$NON-NLS-N$

		initial = new Combo(exceptionComp, SWT.READ_ONLY);
		if (RuntimeUtil.getInstance().getCurrentPrintProject()
				.getReferenceFieldGroup().getItemNames() != null) {
			initial.setItems(RuntimeUtil.getInstance().getCurrentPrintProject()
					.getReferenceFieldGroup().getItemNames());
			initial.select(0);
			exception.setEnabled(true);
		}

		relation = new Combo(exceptionComp, SWT.READ_ONLY);
		for (int i = 0; i < relationItems.length; i++) {
			relation.add(relationItems[i][1]);
		}
		relation.select(0);

		reference = new Combo(exceptionComp, SWT.READ_ONLY);
		for (int i = 0; i < referenceItems.length; i++) {
			reference.add(referenceItems[i][1]);
		}

		reference.select(0);
		reference.addListener(SWT.Selection, new Listener() {

			@Override
			public void handleEvent(Event event) {
				reference_specific.setEnabled(reference.getSelectionIndex() == 1);
				// TODO Auto-generated method stub

			}
		});

		reference_specific = new Text(exceptionComp, SWT.BORDER);
		reference_specific.setLayoutData(reference.getLayoutData());
		reference.notifyListeners(SWT.Selection, new Event());

		// Second row
		label = new Label(exceptionComp, SWT.NONE);

		label = new Label(exceptionComp, SWT.NONE);
		label.setText("dann"); // TODO GuiMessages

		action = new Combo(exceptionComp, SWT.READ_ONLY);
		for (int i = 0; i < actionItems.length; i++) {
			action.add(actionItems[i][1]); // add language dependent terms
		}
		action.select(0);
		action.addListener(SWT.Selection, new Listener() {

			@Override
			public void handleEvent(Event event) {
				actionText.setEnabled(action.getSelectionIndex() == 1);
				// TODO Auto-generated method stub

			}
		});

		actionText = new Text(exceptionComp, SWT.BORDER);
		actionText.setLayoutData(action.getLayoutData());
		action.notifyListeners(SWT.Selection, new Event());

		if (RuntimeUtil.getInstance().getCurrentPrintProject()
				.getReferenceFieldGroup().getItemNames() == null) {
			exception.setSelection(false);
			exception.notifyListeners(SWT.Selection, new Event());
			exception.setEnabled(false);
		}

		Composite footComp = new Composite(this, SWT.BOTTOM);
		footComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		footComp.setLayout(new GridLayout(2, false));

		Button okButton = new Button(footComp, SWT.NONE);
		okButton.setText(GuiMessages.General_Gui_Okay);
		okButton.setLayoutData(new GridData());
		okButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				String referencingName = nameText.getText().trim();

				// check for empty name
				if (referencingName.isEmpty()
						|| referencingName.equalsIgnoreCase("")) {
					MessageDialog.openError(null, "",
							GuiMessages.Stileditor_Error_NoNameForFieldGiven);
					return;
				}

				// check whether name is already used
				boolean used = false;
				if (referencingSystem == null) {
					if (referencingSystemGroup.hasItem(referencingName)) {
						used = true;
					}
				} else {
					AbstractBaseItem refSystem = referencingSystemGroup
							.getItem(referencingName);
					if (refSystem != null
							&& !referencingSystem.equals(refSystem))
						used = true;
				}

				if (used) {
					MessageDialog
							.openError(
									null,
									"",
									GuiMessages.Stileditor_Error_NameForFieldAlreadyUsed);
					return;
				}
				
				// check whether all elements starting with $ are well formed
				//TODO: Maybe one could use getRSFOText() and throw an exception somewhere
				
				
				// write info in maps
				if (referencingSystem == null) {
					referencingSystem = new ReferencingSystem();
					updateInput();
					referencingSystemGroup.add(referencingSystem);
				} else {
					updateInput();
				}
				notifyObservers();
				_this.getShell().close();
			}
		});

		Button cancelButton = new Button(footComp, SWT.NONE);
		cancelButton.setText(GuiMessages.General_Gui_Cancel);
		cancelButton.setLayoutData(new GridData());
		cancelButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				_this.getShell().close();
			}
		});
	}

	// TODO
	private void fillControls() {
		resetControls();

		if (referencingSystem != null) {
			nameText.setText(referencingSystem.getValueOf("name"));
			schemaText.setText(referencingSystem.getValueOf("schema"));
			exception.setSelection(!referencingSystem.getValueOf("exceptions").isEmpty());
			exception.notifyListeners(SWT.Selection, new Event());
			String exceptions_val = referencingSystem.getValueOf("exceptions");
			if (!exceptions_val.trim().isEmpty()) { // analyse structure
				try {
					Document temp = DocumentHelper.parseText(exceptions_val);
					Element e = temp.getRootElement();
					
					initial.setText(e.attributeValue("initial"));
					
					for (int i = 0; i < relationItems.length; i++) {
						if (relationItems[i][0] == e.attributeValue("relation"))
							relation.select(i);
					}
					
					String ref = e.attributeValue("reference"); 
					if (ref.equalsIgnoreCase("last"))
						reference.select(0);
					else {
						reference.select(1);
						reference_specific.setText(ref);
					}
					
					String repl = e.attributeValue("replacement"); 
					if (repl.isEmpty()) { // no replacement text
						action.select(0);
					} else {
						action.select(1);
						actionText.setText(repl);
					}
					
					
				} catch (DocumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			schemaText.setFocus();
		}

		int i = 0;
		counterItemMap.clear();
		String fieldName;
		if (referenceFieldGroup != null) {
			for (AbstractBaseItem field : referenceFieldGroup.getItems()) {
				fieldName = field.getValueOf("name");
				counterItemList.add(fieldName);
				counterItemMap.put(i++, new String[] { fieldName,
						"${" + fieldName + "}" });
			}
		} else {
			counterItemList.add("Keine vorhanden"); //$NON-NLS-N$
		}

		// typographic counters
		i = 0;
		counterItemMap_typographic.clear();
		// Seitenzahl
		counterItemList_typographic.add(counterPageItem[0]);
		counterItemMap_typographic.put(i++, counterPageItem);

		// Spaltenzahl
		counterItemList_typographic.add(counterColumnItem[0]);
		counterItemMap_typographic.put(i++, counterColumnItem);

		// Zeilenummer
		counterItemList_typographic.add(counterRowItem[0]);
		counterItemMap_typographic.put(i++, counterRowItem);

		// special chars
		i = 0;
		counterItemMap_specialChars.clear();
		counterItemList_specialChars.add("Non-breakable space");
		counterItemMap_specialChars.put(i++, new String[] { "nbspace",
				"&#xA0" });

		counterItemList_specialChars.add("{");
		counterItemMap_specialChars.put(i++, new String[] { "{",
				"&#x7B;" });

		counterItemList_specialChars.add("}");
		counterItemMap_specialChars.put(i++, new String[] { "}",
				"&#x7D;" });

		counterItemList_specialChars.add("$");
		counterItemMap_specialChars.put(i++, new String[] { "$",
				"&#x24;" });
		
		counterItemList_specialChars.add("&");
		counterItemMap_specialChars.put(i++, new String[] { "&",
				"&#x26;" });
		
		counterItemList_specialChars.add(";");
		counterItemMap_specialChars.put(i++, new String[] { ";",
				"&#x3B;" });
		

	}

	public ReferencingSystem getReferencingSystem() {
		return referencingSystem;
	}

	/*
	 * Method to build the string needed for the exceptions to a referencing
	 * system
	 */
	private String getRSExceptions() {
		if (exception.getSelection()) {
			return "<exception "
				+ "id=\""
				+ currentId
				+ "\" initial=\""
				+ initial.getText()
				+ "\" relation=\""
				+ relationItems[relation.getSelectionIndex()][0]
				+ "\" reference=\""
				+ ((reference.getSelectionIndex() == 1) ? reference_specific
						.getText() : referenceItems[0][0])
				+ "\" replacement=\""
				+ ((action.getSelectionIndex() == 1) ? actionText.getText()
						: "") + "\"></exception>";
		}
		return null;
	}

	@Override
	public void notifyObservers() {
		for (IObserverControl o : observerList) {
			o.updateControl();
		}
	}

	@Override
	public void registerObserver(IObserverControl observer) {
		observerList.add(observer);
	}

	@Override
	public void removeObserver(IObserverControl observer) {
		observerList.remove(observer);
	}

	private void resetControls() {
		nameText.setText("");
		schemaText.setText("");
		counterItemList.removeAll();
		counterItemList_typographic.removeAll();
		counterItemList_specialChars.removeAll();
		nameText.setFocus();
	}

	public void setInput(ReferencingSystem referencingSystem,
		ReferenceFieldGroup referenceFieldGroup) {
		this.referencingSystem = referencingSystem;
		this.referenceFieldGroup = referenceFieldGroup;
		fillControls();
	}

	public void setReferencingSystemGroup(AbstractBaseGroup rg) {
		this.referencingSystemGroup = (ReferencingSystemGroup) rg;
	}

	private void updateInput() {
		currentId = new Integer(++id).toString();
		referencingSystem.setValueOf("id", currentId);
		referencingSystem.setValueOf("name", nameText.getText());
		referencingSystem.setValueOf("schema", schemaText.getText());
		referencingSystem.setValueOf("exceptions", getRSExceptions());
	}

	
	private Control getTabControl (TabFolder tabFolder, final org.eclipse.swt.widgets.List l, final Map<Integer, String[]> m) {
	    l.addListener(SWT.MouseDoubleClick, new Listener() {
    	@Override
    	public void handleEvent(Event event) {
    		schemaText.forceFocus();
    		schemaText.setCursor(cursor);
    		
    		// We test, whether the cursor is inside {}. Then no insertion is allowed.
    		int cursorpos = schemaText.getCaretPosition();
			
	    	int lcb = schemaText.getText(0,cursorpos).lastIndexOf("{"); 
			if (lcb > -1) { // { found -> look for corresponding }
				if (schemaText.getText(lcb, schemaText.getText().length()).indexOf("}") > cursorpos - lcb - 1)
					return;
			}
    		
			lcb = schemaText.getText(0,cursorpos).lastIndexOf("&"); 
			if (lcb > -1) { // & found -> look for corresponding ;
				if (schemaText.getText(lcb, schemaText.getText().length()).indexOf(";") > cursorpos - lcb - 1 && lcb < cursorpos )
					return;
			}
    		
			schemaText.insert((String) m.get(l.getSelectionIndex())[1]);
	    	}
	    });
	
	    return l;
	}
}
